// Written in the D Programming Language
/**
 * The 6th lesson in the <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=06">NeHe tutorial series</a>.
 * Originally written by Jeff Molofee.
 *
 * Authors: Jeff Molofee
 *          Olli Aalto
 */
module lesson06;

import derelict.opengl.gl;
import derelict.opengl.glu;
import derelict.sdl.sdl;
import derelict.sdl.image;
import tango.io.Stdout;
import tango.stdc.stringz;

/// The window title
const char[] WINDOW_TITLE = "NeHe's Texture Mapping Tutorial (D version)";

/// The main loop flag
bool running;

/// X Rotation
GLfloat xrot = 0.0f;
/// Y Rotation
GLfloat yrot = 0.0f;
/// Z Rotation
GLfloat zrot = 0.0f;

/// Storage For One Texture
GLuint texture;

/**
 * Module constructor. Here we load the GL, GLU and SDL shared libraries,
 * and the initialize SDL.
 */
static this()
{
	DerelictGL.load();
	DerelictGLU.load();
	DerelictSDL.load();
	DerelictSDLImage.load();

	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		throw new Exception("Failed to initialize SDL: " ~ getSDLError());
	}
}

/**
 * Module destructor. SDL_Quit must be called somewhere, and as we initialized
 * it in the module constructor so the module destructor should be a suitable
 * place.
 */
static ~this()
{
	SDL_Quit();
}

/**
 * The main function. This is where the fun begins. The first order of business
 * is the check the command line arguments if the user wanted to start in
 * fullscreen mode. Then the window is created and OpenGL is initialized with
 * basic settings. Finally the the function starts the main loop which will live
 * for the duration of the application.
 *
 * Params:
 *      args = the command line arguments
 */
void main(char[][] args)
{
	bool fullScreen = false;
	if (args.length > 1)
	{
		fullScreen = args[1] == "-fullscreen";
	}

	createGLWindow(WINDOW_TITLE, 640, 480, 32, fullScreen);
	initGL();

	running = true;
	while (running)
	{
		processEvents();

		drawGLScene();

		SDL_GL_SwapBuffers();
		SDL_Delay(10);
	}
}

/**
 * Load the texture used in this tutorial.
 */
void loadGLTexture()
{
	SDL_Surface* textureImage;//=loadImage("res/red_light.bmp");
	Stdout.formatln("test");
	// Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
	if ((textureImage = IMG_Load("res/red_light.png")) !is null)
	//if (textureImage !is null)
	{
		Stdout.formatln("test2");
		//SDL_Surface* tf2=SDL_DisplayFormat(textureImage);
		SDL_Surface* tf2=SDL_DisplayFormatAlpha(textureImage);
		SDL_FreeSurface(textureImage);
		// Free the surface when exiting the scope
		scope(exit)
			SDL_FreeSurface(textureImage);
		textureImage=tf2;
		// Create The Texture
		glGenTextures(1, &texture);
		Stdout.formatln("Size : {} , {}",textureImage.w, textureImage.h);
		
		// Create Nearest Filtered Texture
		glBindTexture(GL_TEXTURE_2D, texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureImage.w, textureImage.h, 0,
			GL_BGRA, GL_UNSIGNED_BYTE, textureImage.pixels);
	}
}
//Loads an image from a file, converting it to the appropriate format
SDL_Surface *loadImage(char* fileName)
{
//Attempt to load the image
SDL_Surface *image;
image = IMG_Load(fileName);
if (! image)
{
Stdout.formatln("Could not load image {}", fileName);
return image;
}

//Check the format
switch (image.format.BitsPerPixel)
{
case 8:
//It's 8 bit, do whatever
break;
case 16:
//It's 16 bit, do whatever
break;
case 24:
//It's 24 bit, so always convert (someone check my pixel format for me!)
SDL_PixelFormat format = {null, 32, 4, 0, 0, 0, 0, 0, 8, 16, 24,  0x00FF0000, 0x0000FF00,0x000000FF,0xFF000000,  0, 255};
SDL_Surface *temp = SDL_ConvertSurface(image, &format, SDL_SWSURFACE);
SDL_FreeSurface(image);
image = temp;
break;
case 32:
//It's 32 bit, so convert only if it's ABGR (and really, check this pixel format!)
if (image.format.Rshift > image.format.Bshift)
{
SDL_PixelFormat format = {null, 32, 4, 0, 0, 0, 0, 0, 8, 16, 24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000, 0, 255};
SDL_Surface *temp = SDL_ConvertSurface(image, &format, SDL_SWSURFACE);
SDL_FreeSurface(image);
image = temp;
}
break;
default:
Stdout.formatln("Unknown image format for {}%s", fileName);
}

return image;
}

/**
 * Process all the pending events.
 */
void processEvents()
{
	SDL_Event event;
	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
			case SDL_KEYUP:
				keyReleased(event.key.keysym.sym);
				break;
			case SDL_QUIT:
				running = false;
				break;
			default:
				break;
		}
	}
}

/**
 * Process a key released event.
 */
void keyReleased(int key)
{
	switch (key)
	{
		case SDLK_ESCAPE:
			running = false;
			break;
		default:
			break;
	}
}

/**
 * Resize and initialize the OpenGL window.
 */
void resizeGLScene(GLsizei width, GLsizei height)
{
	if (height == 0)
	{
		height = 1;
	}
	// Reset The Current Viewport
	glViewport(0, 0, width, height);

	// Select The Projection Matrix
	glMatrixMode(GL_PROJECTION);

	// Reset The Projection Matrix
	glLoadIdentity();

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f, cast(GLfloat) width / cast(GLfloat) height, 0.1f,
		100.0f);

	// Select The Modelview Matrix
	glMatrixMode(GL_MODELVIEW);

	// Reset The Modelview Matrix
	glLoadIdentity();
}

/**
 * Initialize OpenGL.
 */
void initGL()
{
	// Load the texture
	loadGLTexture();
	// Enable Texture Mapping
	glEnable(GL_TEXTURE_2D);
	// Enables Smooth Shading
	glShadeModel(GL_SMOOTH);
	// Black Background
	glClearColor(0.0f, 0.0f, 0.0f, 0f);
	// Depth Buffer Setup
	glClearDepth(1.0f);
	// Enables Depth Testing
	//glEnable(GL_DEPTH_TEST);
	// The Type Of Depth Test To Do
	glDepthFunc(GL_LEQUAL);
	// Really Nice Perspective Calculations
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	// Change to texture matrix and flip and rotate the texture
	glMatrixMode(GL_TEXTURE);
	glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
	glScalef(-1.0f, 1.0f, 1.0f);
	// Back to normal
	glMatrixMode(GL_MODELVIEW);
	glColor4f(1.0f,1.0f,1.0f,1f);			// Full Brightness, 50% Alpha ( NEW )
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
}

/**
 * The drawing function. Now we only clear the color and depht buffers, so that
 * the window stays black.
 */
void drawGLScene()
{
	// Clear The Screen And The Depth Buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// Reset The Current Modelview Matrix
	glLoadIdentity();

	// Move Into The Screen 5 Units
	glDisable(GL_TEXTURE_2D);

	// Select Our Texture
	glScalef(0.1f,0.1f,1f);
	//glColor4f(1f,1f,1f,1f);
	glBegin(GL_QUADS);
	// Back Face
	// Bottom Right Of The Texture and Quad
	glVertex3f(-1.0f, -1.0f, -1.0f);
	// Top Right Of The Texture and Quad
	glVertex3f(-1.0f, 1.0f, -1.0f);
	// Top Left Of The Texture and Quad
	glVertex3f(1.0f, 1.0f, -1.0f);
	// Bottom Left Of The Texture and Quad
	glVertex3f(1.0f, -1.0f, -1.0f);
	glEnd();
	glTranslatef(0.5f,0,-0.2);
		glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, texture);
	
	glBegin(GL_QUADS);
	// Back Face
	// Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(-1.0f, -1.0f, -1.0f);
	// Top Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(-1.0f, 1.0f, -1.0f);
	// Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);
	// Bottom Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);
	glEnd();
	glTranslatef(1f,0,0.1f);
	glBegin(GL_QUADS);
	// Back Face
	// Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(-1.0f, -1.0f, -1.0f);
	// Top Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(-1.0f, 1.0f, -1.0f);
	// Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);
	// Bottom Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);
	
/*
	// Top Face
	// Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-1.0f, 1.0f, -1.0f);
	// Bottom Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-1.0f, 1.0f, 1.0f);
	// Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(1.0f, 1.0f, 1.0f);
	// Top Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);

	// Bottom Face
	// Top Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(-1.0f, -1.0f, -1.0f);
	// Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);
	// Bottom Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, 1.0f);
	// Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(-1.0f, -1.0f, 1.0f);

	// Right face
	// Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, -1.0f);
	// Top Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, -1.0f);
	// Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(1.0f, 1.0f, 1.0f);
	// Bottom Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(1.0f, -1.0f, 1.0f);

	// Left Face
	// Bottom Left Of The Texture and Quad
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-1.0f, -1.0f, -1.0f);
	// Bottom Right Of The Texture and Quad
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(-1.0f, -1.0f, 1.0f);
	// Top Right Of The Texture and Quad
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(-1.0f, 1.0f, 1.0f);
	// Top Left Of The Texture and Quad
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-1.0f, 1.0f, -1.0f);
*/	glEnd();

	// X Axis Rotation
	xrot += 0.3f;
	// Y Axis Rotation
	yrot += 0.2f;
	// Z Axis Rotation
	zrot += 0.4f;
}

/**
 * Initializes and opens the SDL window.
 */
void createGLWindow(char[] title, int width, int height, int bits,
	bool fullScreen)
{
	// Set the OpenGL attributes
	//SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
	//SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
	//SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	// Set the window title
	SDL_WM_SetCaption(toStringz(title), null);

	// Note the SDL_DOUBLEBUF flag is not required to enable double
	// buffering when setting an OpenGL video mode.
	// Double buffering is enabled or disabled using the
	// SDL_GL_DOUBLEBUFFER attribute. (See above.)
	int mode = SDL_OPENGL;
	if (fullScreen)
	{
		mode |= SDL_FULLSCREEN;
	}
	// Now open a SDL OpenGL window with the given parameters
	if (SDL_SetVideoMode(width, height, bits, mode) is null)
	{
		throw new Exception("Failed to open OpenGL window: " ~ getSDLError());
	}

	resizeGLScene(width, height);
}

/**
 * Get the SDL error as a D string.
 *
 * Returns: A D string containing the current SDL error.
 */
char[] getSDLError()
{
	return fromStringz(SDL_GetError());
}
